import { List } from 'linqts';
import { linq } from '@/lib/linq';
import { Assert } from '@/lib/DebugHelper';
import { Flac3dFileContent } from '@/lib/Flac3d/Flac3dData';
import {
    Node,
    Elem,
    Group,
    GroupName,
    GetNode,
    GetElem,
    GetGroupName,
    GetSlices,
    FemMesh
} from '@/lib/FemPrimitives';

export class Flac3dExporter {
    private NodeList = new List<Node>();
    private Elem3DList = new List<Elem>();
    private Elem2DList = new List<Elem>();
    private Elem1DList = new List<Elem>();

    private Elem3DGroups = new List<Group>();
    private Elem2DGroups = new List<Group>();
    private Elem1DGroups = new List<Group>();

    private Elem3DGroupNames = new List<GroupName>();
    private Elem2DGroupNames = new List<GroupName>();
    private Elem1DGroupNames = new List<GroupName>();

    public DoExport(femMesh: FemMesh): Flac3dFileContent {
        Assert(femMesh);

        this.NodeList = femMesh.NodeList;
        this.Elem3DList = femMesh.Elem3DList;
        this.Elem2DList = femMesh.Elem2DList;
        this.Elem1DList = femMesh.Elem1DList;

        //
        // Convert mesh data
        //
        this.Elem3DGroups = femMesh.Elem3DGroups;
        this.Elem2DGroups = femMesh.Elem2DGroups;
        this.Elem1DGroups = femMesh.Elem1DGroups;

        const elemGroupNames = femMesh.ElemGroupNames;
        this.Elem3DGroupNames = this.Elem3DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: `eset_` + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });
        this.Elem2DGroupNames = this.Elem2DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: `eset_` + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });
        this.Elem1DGroupNames = this.Elem1DGroups.Select(g => {
            return {
                GroupId: g.GroupId,
                GroupName: `eset_` + GetGroupName(elemGroupNames, g.GroupId).GroupName
            };
        });

        //
        // Output mesh data
        //
        const gridFileContent = this.CreateFlac3DGridFile();
        const geomFileContent = this.CreateFlac3DGeomFile();

        //
        // Output statistical information
        //
        const femCountInfo = this.GenerateFemCountInfo();

        //
        // return export result
        //
        return {
            GridFileContent: gridFileContent.ToArray(),
            GeomFileContent: geomFileContent.ToArray(),
            MeshCountInfo: femCountInfo.ToArray()
        };
    }

    private CreateFlac3DGridFile(): List<string> {
        const t = new Date();
        const timeStr = `${t.getFullYear()}/${t.getMonth() + 1}/${t.getDate()}-${t.getHours()}:${t.getMinutes()}:${t.getSeconds()}`;
        const content = new List<string>([
            '*',
            '* FLAC3D Mesh generated by ',
            '*   MidasGtsExporter (https://wdhust.gitee.io/gts-exporter)',
            `*     at ${timeStr}`,
            '*'
        ]);

        //
        // node definition
        //
        content.Add('* GRIDPOINTS');

        const allElemNodeIds = this.Elem3DList.SelectMany(e => e.NodeIds).Concat(
            this.Elem2DList.SelectMany(e => e.NodeIds)
        );
        const outputNodeIds = allElemNodeIds.Distinct();
        content.AddRange(
            outputNodeIds
                .Select(nodeId => {
                    const node = GetNode(this.NodeList, nodeId);
                    const id = node.Id;
                    const x = node.x;
                    const y = node.y;
                    const z = node.z;
                    return `G ${id} ${x.toFixed(6)} ${y.toFixed(6)} ${z.toFixed(6)}`;
                })
                .ToArray()
        );

        //
        // zone definition
        //
        function GenElemLine(
            pre: string,
            elem: Elem,
            idIndex: List<number>
        ): string {
            return (
                `${pre} ${elem.Id} ` +
                idIndex
                    .Select(i => `${elem.NodeIds.ElementAt(i - 1)}`)
                    .ToArray()
                    .join(' ')
            );
        }

        if (this.Elem3DList.Any()) {
            //
            // Output zone data
            //
            content.Add('* ZONES');
            const elems = this.Elem3DList;
            linq([
                {
                    NodeCount: 8,
                    Prefix: 'Z B8',
                    NodeIdIndex: [1, 2, 4, 5, 3, 8, 6, 7]
                },
                {
                    NodeCount: 6,
                    Prefix: 'Z W6',
                    NodeIdIndex: [1, 3, 4, 2, 6, 5]
                },
                {
                    NodeCount: 5,
                    Prefix: 'Z P5',
                    NodeIdIndex: [1, 2, 4, 5, 3]
                },
                {
                    NodeCount: 4,
                    Prefix: 'Z T4',
                    NodeIdIndex: [1, 2, 3, 4]
                }
            ]).ForEach(ni => {
                const nodeCount = ni!.NodeCount;
                const prefix = ni!.Prefix;
                const nodeIdIndex = linq(ni!.NodeIdIndex);
                content.AddRange(
                    elems
                        .Where(e => e!.NodeIds.Count() === nodeCount)
                        .Select(e => GenElemLine(prefix, e, nodeIdIndex))
                        .ToArray()
                );
            });
        }

        //
        // Zone group data
        //
        if (this.Elem3DGroups.Any()) {
            content.Add('* ZONE GROUPS');
            this.Elem3DGroups.ForEach(group => {
                const groupName = this.Elem3DGroupNames.First(
                    gn => gn!.GroupId === group!.GroupId
                ).GroupName;
                content.Add(`ZGROUP "` + groupName + `" SLOT 1 `);
                const idCountPerLine = 20;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(' ')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        // face definition
        //
        if (this.Elem2DList.Any()) {
            //
            // face data
            //
            content.Add('* FACES');
            const elems = this.Elem2DList;
            linq([
                {
                    NodeCount: 4,
                    Prefix: 'F Q4',
                    NodeIdIndex: [1, 2, 3, 4]
                },
                {
                    NodeCount: 3,
                    Prefix: 'F T3',
                    NodeIdIndex: [1, 2, 3]
                }
            ]).ForEach(ni => {
                const nodeCount = ni!.NodeCount;
                const prefix = ni!.Prefix;
                const nodeIdIndex = linq(ni!.NodeIdIndex);
                content.AddRange(
                    elems
                        .Where(e => e!.NodeIds.Count() === nodeCount)
                        .Select(e => GenElemLine(prefix, e, nodeIdIndex))
                        .ToArray()
                );
            });
        }

        //
        // face group data
        //
        if (this.Elem2DGroups.Any()) {
            content.Add('* FACE GROUPS');
            this.Elem2DGroups.ForEach(group => {
                const groupName = this.Elem2DGroupNames.First(
                    gn => gn!.GroupId === group!.GroupId
                ).GroupName;
                content.Add(`FGROUP "` + groupName + `" SLOT 2 `);
                const idCountPerLine = 20;
                const idLines = GetSlices(group!.ItemIds, idCountPerLine).Select(ids =>
                    ids
                        .Select(id => `${id}`)
                        .ToArray()
                        .join(' ')
                );
                content.AddRange(idLines.ToArray());
            });
        }

        //
        // Write to file
        //
        return content;
    }

    private CreateFlac3DGeomFile(): List<string> {
        const content = new List<string>(['ITASCA GEOMETRY3D', ';']);

        //
        // Output nodes
        //
        content.Add('NODES');
        const outputNodeIds = this.Elem1DList.SelectMany(
            elem => elem.NodeIds
        ).Distinct();
        content.AddRange(
            outputNodeIds
                .Select(nodeId => {
                    const node = GetNode(this.NodeList, nodeId);
                    const id = node.Id;
                    const x = node.x;
                    const y = node.y;
                    const z = node.z;
                    return `${id} ${x.toFixed(6)} ${y.toFixed(6)} ${z.toFixed(6)}`;
                })
                .ToArray()
        );

        //
        // Output edges
        //
        content.Add('EDGES');
        this.Elem1DGroups.ForEach(group => {
            const groupId = group!.GroupId;
            const groupName = this.Elem1DGroupNames.First(
                gn => gn!.GroupId === groupId
            ).GroupName;
            content.Add(`GROUP "Geom" "${groupName}"`);
            group!.ItemIds.ForEach(elemId => {
                const elem = GetElem(this.Elem1DList, elemId!);
                const n1 = elem.NodeIds.ElementAt(0);
                const n2 = elem.NodeIds.ElementAt(1);
                content.Add(`${elem.Id} ${n1} ${n2}`);
            });
        });

        //
        // Write to file
        //
        return content;
    }

    private GenerateFemCountInfo(): List<string> {
        const nodeCount = this.NodeList.Count();
        const elem3DCount = this.Elem3DList.Count();
        const elem2DCount = this.Elem2DList.Count();
        const elem1DCount = this.Elem1DList.Count();
        const elemCount = elem3DCount + elem2DCount + elem1DCount;

        const femCountInfo = new List<string>([' ****** 输出数据统计 ****** ']);
        femCountInfo.Add(`节点数量：${nodeCount}`);
        if (elemCount > 0) {
            femCountInfo.Add(`单元数量：${elemCount}`);
        }

        if (elem3DCount > 0) {
            femCountInfo.Add(`3D单元数量：${elem3DCount}`);
        }
        if (this.Elem3DGroups.Any()) {
            this.Elem3DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem3DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        if (elem2DCount > 0) {
            femCountInfo.Add(`2D单元数量：${elem2DCount}`);
        }
        if (this.Elem2DGroups.Any()) {
            this.Elem2DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem2DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        if (elem1DCount > 0) {
            femCountInfo.Add(`1D单元数量：${elem1DCount}`);
        }
        if (this.Elem1DGroups.Any()) {
            this.Elem1DGroups.ForEach(g => {
                const count = g!.ItemIds.Count();
                const groupName = GetGroupName(this.Elem1DGroupNames, g!.GroupId)
                    .GroupName;
                femCountInfo.Add(`    ${groupName}: ${count}`);
            });
        }

        return femCountInfo;
    }
}
